#include "predicate.h"

void default_pred_destroy(predicate *p)
{
    free(p);
}

/**
 * The NOT test.
 */
int not_test(predicate *p, struct dirent *entry)
{
    predicate *inner_pred = (predicate *)p->obj1;
    return ! inner_pred->test(inner_pred, entry);
}

void not_pred_destroy(predicate *p)
{
    predicate *inner = (predicate *)p->obj1;
    inner->destroy(inner);
    free(p);
}

/**
 * Builder of NOT predicate.
 */
predicate *not_predicate(predicate *p1)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = p1;
        p->test = not_test;
        p->destroy = not_pred_destroy;
    }

    return p;
}

/**
 * The AND test.
 * Precondition: obj1 and obj2 in predicate must neither be NULL
 */
int and_test(predicate *p, struct dirent *entry)
{
    predicate *p1 = (predicate *)p->obj1;
    predicate *p2 = (predicate *)p->obj2;
    return p1->test(p1, entry) && p2->test(p2, entry);
}

/**
 * The OR test.
 * Precondition: obj1 and obj2 in predicate must neither be NULL
 */
int or_test(predicate *p, struct dirent *entry)
{
    predicate *p1 = (predicate *)p->obj1;
    predicate *p2 = (predicate *)p->obj2;
    return p1->test(p1, entry) || p2->test(p2, entry);
}

/**
 * The binary predicate destroy.
 */
void binary_pred_destroy(predicate *p)
{
    predicate *p1 = (predicate *)p->obj1;
    predicate *p2 = (predicate *)p->obj2;
    p1->destroy(p1);
    p2->destroy(p2);
    free(p);
}

/**
 * Builder of AND test.
 */
predicate *and_predicate(predicate *p1, predicate *p2)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = p1;
        p->obj2 = p2;
        p->test = and_test;
        p->destroy = binary_pred_destroy;
    }

    return p;
}

/**
 * Builder of OR test.
 */
predicate *or_predicate(predicate *p1, predicate *p2)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = p1;
        p->obj2 = p2;
        p->test = or_test;
        p->destroy = binary_pred_destroy;
    }

    return p;
}

/**
 * The name test.
 * Precondition: obj1 in predicate must be a string, 
 * indicating the specified name of file.
 * Currently, every character in obj1 is treated as is.
 */
int name_test(predicate *p, struct dirent *entry)
{
    char *name = (char *)p->obj1;
    return fnmatch(name, entry->d_name, 0) == 0;
}

/**
 * Builder of name predicate.
 */
predicate *name_predicate(char *name)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = name;
        p->test = name_test;
        p->destroy = default_pred_destroy;
    }

    return p;
}

/**
 * The type test.
 * tests if the type of directory entry is the same as configured.
 */
int type_test(predicate *p, struct dirent *entry)
{
    unsigned int type = (unsigned int)p->obj1;
    return type == entry->d_type;
}

/**
 * The type predicate builder.
 */
predicate *type_predicate(unsigned int type)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = (void *)type;
        p->test = type_test;
        p->destroy = default_pred_destroy;
    }

    return p;
}

/**
 * The uid test.
 */
int uid_test(predicate *p, struct dirent *entry)
{
    uid_t uid = (uid_t)p->obj1;

    struct stat stat_buf;
    /* TBD: check return value of lstat */
    lstat(entry->d_name, &stat_buf);

    return uid == stat_buf.st_uid;
}

/**
 * Builder of uid_predicate.
 */
predicate *uid_predicate(uid_t uid)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = (void *)uid;
        p->test = uid_test;
        p->destroy = default_pred_destroy;
    }

    return p;
}

/**
 * Always return true.
 */
int true_atom(predicate *p, struct dirent *entry)
{
    return 1;
}

/**
 * Builder of all true predicate
 */
predicate *true_predicate()
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->test = true_atom;
        p->destroy = default_pred_destroy;
    }

    return p;
}

/**
 * The access/modify/change time predicate.
 */
int amctime_test(predicate *p, struct dirent *entry)
{
    int days_ago = (int)p->obj1;
    enum AMC_FIELDS field = (enum AMC_FIELDS)p->obj2;

    struct stat stat_buf;
    lstat(entry->d_name, &stat_buf);

    time_t time_field;
    switch(field) {
        case ACCESS: time_field = stat_buf.st_atime; break;
        case MODIFY: time_field = stat_buf.st_mtime; break;
        case CHANGE: time_field = stat_buf.st_ctime; break;
    }

    time_t now = time(NULL);
    int days_passed = (now - time_field) / 3600 / 24;
    
    return days_passed >= days_ago;
}

/**
 * Builder of amctime predicate.
 * parameter field:
 *   0 - access time
 *   1 - modify time
 *   2 - change time
 */
predicate *amctime_predicate(int days_ago, enum AMC_FIELDS field)
{
    predicate *p = (predicate *)malloc(sizeof(predicate));
    if(p) {
        p->obj1 = (void *)days_ago;
        p->obj2 = (void *)field;
        p->test = amctime_test;
        p->destroy = default_pred_destroy;
    }

    return p;
}

